#ifndef TNMC_LIMZ_CPP_NETWORK_PROGRAMMING_FRAMEWORK_H
#define TNMC_LIMZ_CPP_NETWORK_PROGRAMMING_FRAMEWORK_H

#include <string>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/timeb.h>

#include <errno.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <stdint.h>

#include <queue>
#include <set>

#include "Logger.h"
#include "HttpParser.h"

/**
 * @brief Event codes
 */
#define TNMC_FW_EVENT_CODE_NETWORK_ERR 506
#define TNMC_FW_EVENT_CODE_EGAIN 507
#define TNMC_FW_EVENT_CODE_CLOSE 508
#define TNMC_FW_EVENT_CODE_DECODING_ERR 508
#define TNMC_FW_EVENT_CODE_COMPLETED 612
#define TNMC_FW_EVENT_CODE_HTTP400 400 
#define TNMC_FW_EVENT_CODE_HTTP416 416 
#define TNMC_FW_EVENT_CODE_HTTP404 404 

#define TNMC_MAX_EPOLL_SIZE 8192
#define TNMC_ALIVE cout << __FILE__ << ":" << __LINE__ << endl;
#define TNMC_SERVER_LISTEN_SIZE 512

#define TNMC_HELPER_BUFFER_LEN 4095 

using namespace std;

typedef long long fw_file_size_t;

struct SockNode;
struct Session;

struct FwAppMsg {
    int type;
    SockNode *sock_node;
    FwAppMsg(int atype, SockNode *snode) : type(atype), sock_node(snode) {}
};

enum FwAppMsgType {
    FW_APP_MSG_TYPE_RECV,
    FW_APP_MSG_TYPE_SEND,
    FW_APP_MSG_TYPE_CONNECTED,
    FW_APP_MSG_TYPE_NETWORK_ERR,
    FW_APP_MSG_TYPE_CLOSE,
};


class MsgQueue {
    public: 
        bool is_msgempty();
        void enqueue_msg(FwAppMsg *msg);
        FwAppMsg *dequeue_msg();
        MsgQueue();
        ~MsgQueue();
    private:
        pthread_mutex_t mutex;
        queue<FwAppMsg*> msg_queue;
};

struct SockNode {
    int sockfd;
    void *data;
    SockNode (int fd) : sockfd(fd), data(0) {}
    ~SockNode() {}
};

class Session {
    public:
        int GetFd() { return sockfd; }
        SockNode *GetSockNode() { return sn; }
        Session (SockNode *node);
        void Lock() {
            assert(locked == false);
            pthread_mutex_lock(&mutex);
            locked = true;
        }
        void UnLock() {
            assert(locked == true);
            pthread_mutex_unlock(&mutex);
            locked = false;
        }
        virtual ~Session();
    protected:
        int sockfd;
        SockNode *sn;
        uint64_t start_time;
        double bytes_per_ms;
        pthread_mutex_t mutex;  
        bool locked;
        friend class FwNetworking;
};


class AppNode;
class FwNetworking {
    public:
        FwNetworking(AppNode *an_app);  
        ~FwNetworking();  
        /**
         * @brief Send content of a std::string via socket 
         *
         * @param sockfd
         * @param datagram
         *
         * @return 0 on success, -1 for failure 
         */
        int SendString(int sockfd, string datagram);
        int SendFileBlock(int sockfd, char *buf, size_t len);
        /**
         * @brief A wrapper for POSIX recv 
         *
         * @return a positive number of bytes received
         */
        int ReadBlock(int sockfd, char *buf, size_t len) throw(int);
        /**
         * @brief A wrapper for POSIX recv with MSG_PEEK flag specified
         *
         * @return a positive number of bytes received 
         */
        int PeekBlock(int sockfd, char *buf, size_t len) throw(int);
        void RunEpoll();
        int StartListening(int port);
        void GetLocalSockIp(int sockfd, string &local_ip_text);
        /**
         * @brief Accept client connecting request. For server use.
         *
         * @throw SockNode*
         */
        void AcceptConn() throw (SockNode*);
        void DelSockNode(SockNode *sock_node) {
            CloseConn(sock_node);
            delete sock_node;
        }
        void CloseConn(SockNode *sock_node) {
            if (sock_node->sockfd) {
                epoll_ctl(epollfd, EPOLL_CTL_DEL, sock_node->sockfd, NULL);
                close(sock_node->sockfd);
                sock_node->sockfd = 0;
            }
        }
        /**
         * @brief Start connecting to specified server in a non-blocking manner. For client use.
         *
         * @param server_ip
         * @param server_port
         *
         * @return connecting socket fd, or -1 on error 
         */
        int InitConn(uint32_t server_ip, uint16_t server_port);
        static int SetNonBlocking(int sockfd);
    private:
        AppNode *app;
        SockNode *listen_sock;
        int epollfd;
};

class AppNode {
    public:
	AppNode(uint32_t timeout_usec = 1000000);
	~AppNode();
        virtual void ExecuteWorkingThread();
        virtual void ExecuteNetworkingThread() {
            networker.RunEpoll();
        }

        virtual void AddWork(FwAppMsg *msg) {
            work_queue.enqueue_msg(msg);
        }

        virtual void *AddSession(SockNode *sock_node) {
            Session *session = ConstructSession(sock_node);
            active_sessions.insert(session);
            return (void *)session;
        }

        virtual void AddConnectingSession(SockNode *sock_node) {
            Session *session = static_cast<Session *>(sock_node->data);
            connecting_sessions.insert(session);
            set_timeout(); 
        }

        virtual void DelConnectingSession(SockNode *sock_node) {
            Session *session = static_cast<Session *>(sock_node->data);
            connecting_sessions.erase(session);
        }

    protected:
        virtual void DelSession(Session *session) {
            if (active_sessions.find(session) == active_sessions.end()) return;
            if (session->GetSockNode()) {
                networker.DelSockNode(session->GetSockNode());
            }
            active_sessions.erase(session);
            delete session;
        }

        /**
         * @brief Assure parameter points to a legal buffer
         *
         * @param sock_node
         *
         * @return true if legal 
         */
        virtual bool VerifySockNode(SockNode *sock_node) {
            Session *session = static_cast<Session *>(sock_node->data);
            if (active_sessions.find(session) == active_sessions.end()) return false;
            if (sock_node && session->GetSockNode() != sock_node) return false;
            return true;
        }

        /**
         * @brief A session factory 
         *
         * @param sock_node
         *
         * @return created session 
         */
        virtual Session *ConstructSession(SockNode *sock_node) { return (new Session(sock_node)); }
        virtual void HandleRecvMsg(FwAppMsg *msg) = 0;
        virtual void HandleTimeout();

        virtual void HandleConnedMsg(FwAppMsg *msg) { 
            DelConnectingSession(msg->sock_node);
            delete msg;
        }
        virtual void HandleSendMsg(FwAppMsg *msg) = 0;
        virtual void HandleCloseMsg(FwAppMsg *msg) {
            Session *session = static_cast<Session *>(msg->sock_node->data);
            if (session) DelSession(session);
            delete msg;
        }

        virtual void HandleNetworkErrMsg(FwAppMsg *msg) {
            Session *session = static_cast<Session *>(msg->sock_node->data);
            if (session) DelSession(session);
            delete msg;
        }

        uint64_t get_time_ms();
        void Respond416(int fd);
        void Respond404(int fd);
        void Respond400(int fd);
        void Respond201(int fd, const char *content_type, string txt);
        void Respond206(int fd, fw_file_size_t start, fw_file_size_t end, fw_file_size_t filelen);
        void set_timeout() {
            gettimeofday(&timeout_time, NULL);
            timeout_time.tv_sec += m_timeo_sec;
            uint32_t usec = timeout_time.tv_usec + m_timeo_usec;
            if (usec > 1000000) {
                timeout_time.tv_sec += 1;
                timeout_time.tv_usec = (usec - 1000000);
            } else {
                timeout_time.tv_usec = usec;
            }
        }

        bool check_timeout() {
           struct timeval now; 
           gettimeofday(&now, NULL);
           if (now.tv_sec < timeout_time.tv_sec) return false;
           if (now.tv_sec > timeout_time.tv_sec) return true;
           return (now.tv_usec > timeout_time.tv_usec);
        }

        void show_rate () {
           struct timeval now; 
           gettimeofday(&now, NULL);
           uint64_t usec = (now.tv_sec - m_rate_time.tv_sec) * 1000000 + 
               now.tv_usec - m_rate_time.tv_usec;
           uint64_t bytes_rd = m_bytes_rd - m_bytes_rd_save;
           uint64_t bytes_wr = m_bytes_wr - m_bytes_wr_save;
           double out_rate = bytes_wr/(double)usec;
           double in_rate = bytes_rd/(double)usec;
           cout << "Sessions: " << active_sessions.size() << ". Rate: "<< out_rate << "MB/s(Out), " << 
               in_rate << "MB/s(In)" << endl;
           m_bytes_rd_save = m_bytes_rd;
           m_bytes_wr_save = m_bytes_wr;
           m_rate_time = now;
        }

        /**
         * @brief Update traffic statistics 
         *
         * @param nbytes recent traffic in bytes
         * @param rx specifies directory where true for RX and false for TX
         */
        void AddByteCount(uint64_t nbytes, bool rx) {
            pthread_mutex_lock(&stat_mutex);
            if (rx) {
                m_bytes_rd += nbytes;
            } else {
                m_bytes_wr += nbytes;
            }
            pthread_mutex_unlock(&stat_mutex);
        } 

        /**
         * @brief all sessions for this app
         */
        set<Session *> active_sessions;
        /**
         * @brief client sessions in the middle of connecting
         */
        set<Session *> connecting_sessions;

        /**
         * @brief helper buffer for any temporary uses 
         */
        char *text_buf;

        /**
         * @brief http parser for incoming message
         */
        HttpParser parser; 

        /**
         * @brief Networking communication
         */
        FwNetworking networker;
        MsgQueue work_queue;

        /**
         * @brief Timeout function
         */
        struct timeval start_time;
        struct timeval timeout_time;
        uint32_t m_timeo_usec;
        uint32_t m_timeo_sec;

        /**
         * @brief load statistics
         */
        uint64_t m_bytes_rd;
        uint64_t m_bytes_wr;
        uint64_t m_bytes_rd_save;
        uint64_t m_bytes_wr_save;
        struct timeval m_rate_time;
        pthread_mutex_t stat_mutex;
        friend class FwNetworking;
};

void* process_do_app(AppNode *app);
void* process_do_networking(AppNode *app);





#endif

